/******************************************************
*  Copyright(c) pages.ilovezxl.top
*  FileName : gpio_linux.c
*  Author : rivulet.dave
*  Description : GPIO引脚接口Linux平台实现
*  Data : 2019-04-15
******************************************************/

/*********************************************************************
 * INCLUDES
 */

#include "stdrope.h"
#include "util.h"

#include "hallib.h"
#include "halagent.h"

#if (HAL_GPIO == _ENABLE && HAL_PRODUCER == _HAL_LINUX_)

#include <pthread.h>
#include <linux/input.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>

#include "log.h"
#include "gpio_linux.h"

/*********************************************************************
 *  EXTERNAL VARIABLES
 */

/*********************************************************************
 * MACROS
 */

#define MOD_NAME                "HW_GPIO"
#define GPIO_REG_START_ADD      (0x209c000)
#define GPIO_REG_GROUP_SIZE     (0x4000)

enum _gpio_in_key
{
    IO_KEY_2 = 2,
    IO_KEY_3 = 3,
    IO_KEY_4 = 4,
    IO_KEY_5 = 5,
};

/*********************************************************************
 * TYPES
 */

typedef struct _gpio_in_dev
{
    int32_t iGpioIndex;         //对应gpio.h中定义的GPIO INDEX
    int32_t iPinPort;           //io group
    int32_t iPinNum;            //io num
    int32_t iPinState;          //io state
    int32_t iKeyCode;           //io key code
    int32_t (*pfIOSignalRising)(void *arg);          //input io signal appear callback
    int32_t (*pfIOSignalFalling)(void *arg);       //input io signal disappear callback
} GPIO_IN_DEV_S;

typedef struct _gpio_out_dev
{
    int32_t iGpioIndex;         //对应gpio.h中定义的GPIO INDEX
    int32_t iPinPort;           //io group
    int32_t iPinNum;            //io num
    int32_t iPinState;          //io state
    int32_t iFd;                //io output fd
    const char *pPinName;             //dev node name
}GPIO_OUT_DEV_S;

/*********************************************************************
 * FUNCTIONS DECLARE
 */

static int32_t GPIO_ReadOnceByLinux(uint32_t uiGpioIndex, uint8_t *pUserBuf, uint32_t uiSize, uint32_t uiFlag);
static int32_t GPIO_WriteOnceByLinux(uint32_t uiGpioIndex, const uint8_t *pUserData, uint32_t uiSize, uint32_t uiFlag);
static int32_t GPIO_CtrlByLinux(hd_t hModuleDescriptor, uint32_t uiCmdKey, void *pValue);
static int32_t GPIO_HandleInit(void);
static void GPIO_OutputStateInit(void);
static void GPIO_InputStateInit(void);
static void *GPIO_InputEventProcess(void *arg);

/*********************************************************************
 * VARIABLES
 */

static int32_t s_hGpioHandle = _ERR;
static HAL_OPER_S s_stGpioLinuxObject;
static GPIO_IN_DEV_S s_stGpioInput[] = HAL_GPIO_IN_LIST;
static GPIO_OUT_DEV_S s_stGpioOutput[] = HAL_GPIO_OUT_LIST;

/*********************************************************************
 * FUNCTIONS
 */

/**
 *   @Description: GPIO初始化相关接口，指针
 *   @param : NA
 *   @return ：_OK/_ERR
 */
HAL_OPER_S *GPIO_LinuxObjNew(void)
{
    pthread_t iThreadId;

    GPIO_HandleInit();
    GPIO_InputStateInit();
    GPIO_OutputStateInit();

    s_stGpioLinuxObject.pThis = &s_stGpioLinuxObject;
    s_stGpioLinuxObject.pfOpen = NULL;
    s_stGpioLinuxObject.pfClose = NULL;
    s_stGpioLinuxObject.pfLock = NULL;
    s_stGpioLinuxObject.pfUnlock = NULL;
    s_stGpioLinuxObject.pfRead = NULL;
    s_stGpioLinuxObject.pfWrite = NULL;
    s_stGpioLinuxObject.pfReadOnce = GPIO_ReadOnceByLinux;
    s_stGpioLinuxObject.pfWriteOnce = GPIO_WriteOnceByLinux;
    s_stGpioLinuxObject.pfCtrl = GPIO_CtrlByLinux;
    
    pthread_create(&iThreadId, NULL, GPIO_InputEventProcess, NULL);
    return &s_stGpioLinuxObject;
}

/**
 *   @Description: GPIO去初始化、退出
 *   @param : NA
 *   @return ：_OK/_ERR
 */
int32_t GPIO_LinuxObjDelete(void)
{
    return _OK;
}

/**
 *   @Description: 读取GPIO引脚状态
 *   @param : uiGpioIndex - GPIO索引
 *   @param : pUserBuf - 缓存
 *   @param : uiSize - 缓存大小
 *   @param : uiFlag - 属性标记
 *   @return: _OK/_ERR 读取的数据大小
 */
static int32_t GPIO_ReadOnceByLinux(uint32_t uiGpioIndex, uint8_t *pUserBuf, uint32_t uiSize, uint32_t uiFlag)
{
    GPIO_STATE_E *penState = (GPIO_STATE_E *)(pUserBuf);

    if (!pUserBuf || uiSize < sizeof(GPIO_STATE_E))
    {
        return _ERR;
    }

    for (uint32_t i = 0; i < _ARRAY_SIZE(s_stGpioInput); i++)
    {
        if (s_stGpioInput[i].iGpioIndex == uiGpioIndex)
        {
            *penState = s_stGpioInput[i].iPinState;
            return sizeof(GPIO_STATE_E);
        }
    }

    for (uint32_t i = 0; i < _ARRAY_SIZE(s_stGpioOutput); i++)
    {
        if (s_stGpioOutput[i].iGpioIndex == uiGpioIndex)
        {
            *penState = s_stGpioOutput[i].iPinState;
            return sizeof(GPIO_STATE_E);
        }
    }

    return _ERR;
}

/**
 *   @Description: 设置GPIO引脚状态
 *   @param uiGpioIndex - GPIO索引
 *   @param pUserData - 要写入的数据
 *   @param uiSize - 数据大小
 *   @param uiFlag - 属性标记
 *   @return ：_OK/_ERR 写入的数据大小
 */
static int32_t GPIO_WriteOnceByLinux(uint32_t uiGpioIndex, const uint8_t *pUserData, uint32_t uiSize, uint32_t uiFlag)
{
    int32_t iRet;
    GPIO_STATE_E enState;
    GPIO_OUT_DEV_S *pstPinOutDev = NULL;
    
    if (!pUserData || uiSize != sizeof(GPIO_STATE_E))
    {
        return _ERR;
    }

    for (uint32_t i = 0; i < _ARRAY_SIZE(s_stGpioOutput); i++)
    {
        if (s_stGpioOutput[i].iGpioIndex == uiGpioIndex)
        {
            pstPinOutDev = &s_stGpioOutput[i];
            break;
        }
    }

    if (NULL == pstPinOutDev)
    {
        return _ERR;
    }

    enState = *(GPIO_STATE_E *)pUserData;
    if (GPIO_HIGH == enState)
    {
        iRet = write(pstPinOutDev->iFd, "1", 1);
    }
    else if (GPIO_LOW == enState)
    {
        iRet = write(pstPinOutDev->iFd, "0", 1);
    }
    else
    {
        LOG_ERR(MOD_NAME, "gpio write value invalid:%d\n", enState);
        return _ERR;
    }

    if (1 == iRet)
    {
        pstPinOutDev->iPinState = enState;
    }
    else
    {
        LOG_ERR(MOD_NAME, "gpio write error, idx:%d, iRet:%d, err:%s\n", uiGpioIndex, iRet, strerror(errno));
    }
    return sizeof(enState);
}

/**
 *   @Description: HAL层模块参数控制
 *   @param : hModuleDescriptor - 模块描述符
 *   @param : uiCmdKey - 命令关键字
 *   @param : pValue - 数据
 *   @return:_OK/_ERR
 */
int32_t GPIO_CtrlByLinux(hd_t hModuleDescriptor, uint32_t uiCmdKey, void *pValue)
{
    uint32_t i;
    GPIO_IN_DEV_S *pstPinInDev = NULL;
    HAL_GPIO_CTRL_S *pstCtrlCB = (HAL_GPIO_CTRL_S *)pValue;
    
    if (!pValue)
    {
        return _ERR;
    }

    for (i = 0; i < _ARRAY_SIZE(s_stGpioInput); i++)
    {
        if (s_stGpioInput[i].iGpioIndex == pstCtrlCB->iIdx)
        {
            pstPinInDev = &s_stGpioInput[i];
            break;
        }
    }

    if (NULL == pstPinInDev)
    {
        return _ERR;
    }
    
    if (_GET_BIT(pstCtrlCB->iState, GPIO_RISE_EDGE))
    {
        pstPinInDev->pfIOSignalRising = pstCtrlCB->pfIOSignalCallback;
    }

    if (_GET_BIT(pstCtrlCB->iState, GPIO_FALL_EDGE))
    {
        pstPinInDev->pfIOSignalFalling = pstCtrlCB->pfIOSignalCallback;
    }
    
    return _OK;
}

static int32_t GPIO_ReadIOMemory(void *pOut, size_t iLength, off_t iOffset)
{
	uint8_t *pData = NULL;
	int32_t iRet = -1;
	off_t iPageSize = getpagesize();
	size_t iActualLen = 0;
	off_t iActualOffset = 0;

	if (NULL == pOut || 0 == iLength)
    {
		errno = EINVAL;
		goto ENDS;
	}

	int32_t iFd = open("/dev/mem", O_RDWR | O_SYNC);
	if (iFd < 0)
    {
		goto ENDS;
	}

	iActualOffset = iOffset & ~(iPageSize - 1);
	iActualLen = iLength + (iOffset - iActualOffset);

	pData = mmap(NULL, iActualLen, PROT_READ, MAP_SHARED, iFd, iActualOffset);
	if (NULL == pData)
    {
		goto ENDS;
	}

	memmove(pOut, pData + (iOffset - iActualOffset), iLength);

	/* success */
	iRet = 0;
ENDS:
	if (iFd) 
    {
		close(iFd);
		iFd = -1;
	}
	if (pData)
    {
		munmap(pData, iLength);
		pData = NULL;
	}
	return iRet;
}

static void GPIO_InputStateInit(void)
{
    int32_t i = 0;
    int32_t iReg = 0;
    GPIO_IN_DEV_S *pstPinInDev = NULL;

    for (i = 0; i < _ARRAY_SIZE(s_stGpioInput); i++)
    {
        pstPinInDev = &s_stGpioInput[i];
        GPIO_ReadIOMemory(&iReg, sizeof(int32_t), ((pstPinInDev->iPinPort - 1) * GPIO_REG_GROUP_SIZE) + GPIO_REG_START_ADD);
        pstPinInDev->iPinState = !((iReg >> pstPinInDev->iPinNum) & 0x1);
        LOG_DBG(MOD_NAME, "iPinValue[%d] = %d\n", i, pstPinInDev->iPinState);
    }
    return;
}

static void GPIO_OutputStateInit(void)
{
    GPIO_OUT_DEV_S *pstPinOutDev = NULL;
    int32_t i = 0;
    char buffer[200] = { 0 };

    for (i = 0; i < _ARRAY_SIZE(s_stGpioOutput); i++)
    {
        pstPinOutDev = &s_stGpioOutput[i];
        sprintf(buffer, "/sys/class/leds/%s/brightness", pstPinOutDev->pPinName);
        pstPinOutDev->iFd = open(buffer, O_RDWR | O_NOCTTY | O_NDELAY);
        if (pstPinOutDev->iFd < 0)
        {
            LOG_ERR(MOD_NAME, "open gpio dev: %s err\n", buffer);
            continue;
        }

        if (GPIO_HIGH == pstPinOutDev->iPinState)
        {
            write(pstPinOutDev->iFd, "1", 1);
        }
        else if (GPIO_LOW == pstPinOutDev->iPinState)
        {
            write(pstPinOutDev->iFd, "0", 1);
        }
        else
        {
            LOG_ERR(MOD_NAME, "write state invalid:%d\n", pstPinOutDev->iPinState);
        }
    }
    return;
}

static int32_t GPIO_HandleInit(void)
{
    int32_t iKeyFd;
	char pCmd[128] = { 0 };
	char pData[32] = { 0 };
	FILE *pFp;

	snprintf(pCmd, sizeof(pCmd), "cat /proc/bus/input/devices | grep -A 4 'gpio-key' | grep Handler | awk '{print $NF}'");
	pFp = popen(pCmd, "r");
	if (!pFp)
    {
		return _ERR;
    }

	if (NULL == fgets(pData, sizeof(pData), pFp))
	{
		pclose(pFp);
		return _ERR;
	}
	pclose(pFp);
	pData[strlen(pData)-1] = 0;
	memset(pCmd, 0, sizeof(pCmd));
	snprintf(pCmd, sizeof(pCmd), "/dev/input/%s", pData);
	LOG_INF(MOD_NAME, "DIR: %s\n", pCmd);
    iKeyFd = open(pCmd, O_RDONLY);
    if (iKeyFd <= 0)
    {
        LOG_ERR(MOD_NAME, "can't open key device file\n");
    }
    
    s_hGpioHandle = iKeyFd;
    return _OK;
}

static void *GPIO_InputEventProcess(void *arg)
{
    int32_t iRet;
    struct input_event stKeyEvent;
    int32_t iCode;
    int32_t iValue;
    GPIO_IN_DEV_S *pstPinInDev = NULL;

    while (1)
    {
        if (0 > s_hGpioHandle)
        {
            usleep(10000);
            continue;
        }

        iRet = read(s_hGpioHandle, (unsigned char*)&stKeyEvent, sizeof(struct input_event));
        if (iRet < 0)
        {
            LOG_ERR(MOD_NAME, "read key event failed: %d\n", iRet);
            continue;
        }
    
        if (stKeyEvent.type == EV_SYN)
        {
            continue;
        }
    
        iCode = stKeyEvent.code;
        iValue = stKeyEvent.value;
    
        for (uint32_t i = 0; i < _ARRAY_SIZE(s_stGpioInput); i++)
        {
            if (s_stGpioInput[i].iKeyCode == iCode)
            {
                pstPinInDev = &s_stGpioInput[i];
                break;
            }
        }

        if (NULL == pstPinInDev)
        {
            LOG_ERR(MOD_NAME, "read io invalid, code:%d, value:%d\n", iCode, iValue);
            continue;
        }
    
        pstPinInDev->iPinState = iValue;

        LOG_DBG(MOD_NAME, "GPIO Event, index:%d, iCode:%d, iValue:%d, iPinPort:%d, iPinNum:%d\n",
                pstPinInDev->iGpioIndex, iCode, iValue, pstPinInDev->iPinPort, pstPinInDev->iPinNum);

        if ((iValue == GPIO_HIGH) && (NULL != pstPinInDev->pfIOSignalRising))
        {
            pstPinInDev->pfIOSignalRising(NULL);
        }

        if ((iValue == GPIO_LOW) && (NULL != pstPinInDev->pfIOSignalFalling))
        {
            pstPinInDev->pfIOSignalFalling(NULL);
        }
    }

    return NULL;
}
#endif  // HAL_GPIO == _ENABLE && HAL_PRODUCER == _HAL_LINUX_

/************************************** The End Of File **************************************/

